feat(runtime):增加工具调用重复循环熔断机制#303
Conversation
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Yumiue <188874804+Yumiue@users.noreply.github.com>
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Yumiue <188874804+Yumiue@users.noreply.github.com>
test: add coverage for skill session/runtime/context branches
|
/code 修复编译错误以及review提到的问题 |
处理结果已按触发指令完成“修复编译错误以及 review 提到的问题”。 变更摘要
验证结果
提交与推送
未完成项:无。 |
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
- 将 Validate 中 MaxRepeatCycleStreak 判断由 < 0 回退为 <= 0,与 ApplyDefaults / resolveRepeatCycleStreakLimit / 内联兜底三处保持一致 - 在 TestRuntimeConfigApplyDefaults 补充负值回退测试(-1 被替换为默认值) - 在 TestRuntimeConfigValidate 将有效测试入参从 0 改为 1,并将 0 纳入 无效值覆盖列表,覆盖 <= 0 分支 - 在 TestRepeatCycleStreakStopsRunAndInjectsReminder 补充 EventStopReasonDecided 事件断言(StopReasonError + detail 字段), 与 TestProgressStreakStopsRun 保持对称覆盖 Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
fix(runtime): 修复编译错误并补齐重复循环回归测试
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
|
/code 提高测试覆盖率 |
完成情况已按你的要求提升本 PR 的测试覆盖率,并将变更推送到 本次改动
验证结果
未执行
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
feat(context): 接入 session 级 skills 注入并修正 runtime 降级语义
…lization Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Yumiue <188874804+Yumiue@users.noreply.github.com>
…ool-result-projection fix(context): 修复 metadata-only 工具结果语义丢失
|
/review |
There was a problem hiding this comment.
Found one noteworthy issue in the repeat-cycle breaker logic.
The current implementation only increments RepeatCycleStreak when a round has progress evidence (len(records) > 0). That means repeated failing tool calls with identical arguments never count toward repeat-cycle detection, so the new circuit breaker can be bypassed in the exact “stuck loop” scenario it is meant to stop.
| next := state.LastScore | ||
| isRepeated := false | ||
|
|
||
| if len(records) > 0 { |
There was a problem hiding this comment.
isRepeated is gated by len(records) > 0, and when len(records)==0 you also clear LastSignature below. This prevents repeat-loop detection for repeated failing tool calls (same tool + same args), because failed rounds never accumulate RepeatCycleStreak.
Impact: loops with identical failing calls are only limited by MaxNoProgressStreak, not MaxRepeatCycleStreak, which weakens the new breaker.
Suggestion: compare currentSignature against LastSignature whenever a tool call was attempted (signature non-empty), independent of success evidence; keep/reset signature based on whether a tool-call turn occurred, not whether evidence was positive.
| next := state.LastScore | ||
| isRepeated := false | ||
|
|
||
| if len(records) > 0 { |
There was a problem hiding this comment.
isRepeated 只在 len(records) > 0 时才会计算,且 records 目前仅由“工具成功且非 error”生成。这会导致“同一工具+同一参数的连续失败调用”不会进入 RepeatCycleStreak,与 PR 描述中“重复调用即判定并熔断”不一致。建议将重复判定与 records 解耦:只要本轮存在 tool call 且签名与 LastSignature 相同就累加 RepeatCycleStreak,再单独用 records 决定 NoProgressStreak/ScoreDelta。
| @@ -143,13 +177,25 @@ func (s *Service) Run(ctx context.Context, input UserInput) (err error) { | |||
| } | |||
There was a problem hiding this comment.
当前重复循环判定依赖 evidence,而 evidence 仅在非错误 tool message 时才会写入(见同一逻辑块)。这会让“相同工具 + 相同参数 + 持续失败”的循环不计入 RepeatCycleStreak,只能走 NoProgressStreak,从而绕过重复循环提醒/熔断语义。建议将“重复调用检测”与“调用是否成功”解耦:只要本轮发生工具调用且签名与上轮一致,就应累计重复 streak。
| repeatLimit = config.DefaultMaxRepeatCycleStreak | ||
| } | ||
|
|
||
| if repeatStreak >= repeatLimit { |
There was a problem hiding this comment.
这里存在阈值语义偏移:repeatStreak 是在“与上一轮相同”时才递增,因此 MaxRepeatCycleStreak=3 时实际会在第 4 次相同调用才熔断(提醒也会晚一轮)。这与 PR 描述中的“第 3 次熔断 / 第 2 次提醒”不一致。建议统一计数语义或调整比较条件,避免 off-by-one 行为。
| next := state.LastScore | ||
| isRepeated := false | ||
|
|
||
| if len(records) > 0 { |
There was a problem hiding this comment.
isRepeated is currently gated by len(records) > 0, while failed tool-call rounds produce empty records. Combined with clearing LastSignature on len(records)==0, identical failing calls never increment RepeatCycleStreak, so this breaker can be bypassed in repeated-failure loops. Consider decoupling repeat-signature tracking from success evidence.
| repeatLimit = config.DefaultMaxRepeatCycleStreak | ||
| } | ||
|
|
||
| if repeatStreak >= repeatLimit { |
There was a problem hiding this comment.
Given current counting semantics, MaxRepeatCycleStreak=3 breaks on the 4th identical call (and reminder arrives one turn later than the PR description). If intended behavior is "warn on 2nd, break on 3rd identical call", adjust threshold comparison or streak increment timing to align implementation with documented behavior.
|
/simplify |
完成结果已按指令完成:读取历史评论并处理值得修复的问题、再进行代码简化,并已提交推送。 已处理的 review 问题
代码与测试改动
主要调整:
验证
提交与推送
未执行
|
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: phantom5099 <245659304+phantom5099@users.noreply.github.com>
fix(runtime): align repeat-cycle breaker semantics and tests
描述
当前我们在处理 Agent 的自主执行时,会遇到模型陷入“复读机”状态的情况——即连续多次调用同一个工具,且传入完全相同的参数,导致白白消耗 Token 甚至引发死循环。
本 PR 引入了“重复调用判定与熔断机制”,用于在模型陷入重复死循环时尽早发出自愈警告,并在达到上限时强制打断运行。
主要改动
internal/config):RuntimeConfig中新增了MaxRepeatCycleStreak配置项(默认值为 3),与现有的MaxNoProgressStreak并列。internal/runtime/controlplane/progress.go):ProgressState新增LastSignature字段,用于记录上一轮工具调用的签名特征。ApplyProgressEvidence,当检测到当前签名与上一轮一致时,累加RepeatCycleStreak(重复循环计数)。internal/runtime/run.go&internal/runtime/run_lifecycle.go):computeToolSignature函数,对传入的工具及参数做 JSON 序列化规范化(消除空格、换行等格式干扰)并计算 SHA256 哈希值。ErrRepeatCycleLimit错误定义。MaxRepeatCycleStreak - 1时,向 System Prompt 注入selfHealingRepeatReminder,提醒模型停止复读。MaxRepeatCycleStreak时,直接返回错误中断运行。internal/runtime):runtime_test.go中原有的死循环测试用例,在 mock 参数中加入了动态自增字段,以防止新机制误杀原有的无进展 (NoProgress) 测试链路。核心机制原理解析
新增的逻辑构成了一套 “检测 -> 警告 -> 熔断” 的完整防御机制,具体运作流程如下:
1. 计算操作签名 (Signature)
每次模型回复并要求调用工具时,
run.go会调用新加的computeToolSignature方法。这个方法会遍历本次的所有工具调用(比如
filesystem_edit),并尝试解析它的 JSON 参数,再重新规范化序列化。2. 对比并积累连击 (Streak Tracking)
在工具执行完毕后,调用
ApplyProgressEvidence:LastSignature进行对比。RepeatCycleStreak(重复连击数) 增加。3. 触发自愈提醒 (Self-Healing)
在准备下一轮询问模型(
prepareTurnSnapshot)时,引擎会检查连击数。假设配置的最高忍耐次数(
MaxRepeatCycleStreak)为 3:selfHealingRepeatReminder),提醒模型停止重复无效动作并尝试更换参数或工具,给予模型一次自我纠正的机会。4. 强制熔断 (Circuit Breaker)
如果模型未能自我纠正,在第 3 次 依然发起了完全相同的请求:
引擎会在主循环中进行拦截并抛出
ErrRepeatCycleLimit错误,强制中断运行。这不仅防止了陷入无限计费的 Token 黑洞,也能够及时将控制权归还给用户。